﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using RoMBot.Database;
using RoMBot.MemoryLib;
using RoMBot.Properties;

namespace RoMBot.Classes
{
    public class Inventory
    {
        private readonly List<InventoryItem> bagSlots = new List<InventoryItem>();
        const string SelectStrategyMinstack = "minstack";
        const string SelectStrategyDefault = "best";
        private static readonly List<string> ColorQuality = new List<string> { "white", "green", "blue", "purple", "orange" };

        public Inventory()
        {
            Helpers.RoMScript( "ToggleBackpack(), BagFrame:Hide()" ); // Make sure the client loads the tables first.
            Helpers.RoMScript( "GoodsFrame:Show(), GoodsFrame:Hide()" ); // Make sure the client loads the tables first.
            this.MaxSlots = 240;
            this.Money = Memory.ReadInt( Addresses.moneyPtr );

            if ( Bot.Player != null && Bot.Player.Profile != null )
            {
                //this.MaxSlots = Bot.Player.Profile.GetOptionValue<int>( "INV_MAX_SLOTS" );
            }

            for ( var i = 1; i < this.MaxSlots; i++ )
            {
                this.bagSlots.Add( new InventoryItem( i ) );
            }

            this.Money = Memory.ReadInt( Addresses.moneyPtr );
        }

        /// <summary>
        /// Full update
        /// </summary>
        public void Update()
        {
            var _maxSlots = 60;
            if ( Bot.Player != null && Bot.Player.Profile != null )
            {
                _maxSlots = Bot.Player.Profile.GetOptionValue<int>( "INV_MAX_SLOTS" );
            }
            this.MaxSlots = _maxSlots + 60;
            this.Money = Memory.ReadInt( Addresses.moneyPtr );
            foreach ( var item in this.bagSlots )
            {
                item.Update();
            }
        }

        /// <summary>
        /// Gets the total item count.
        /// </summary>
        /// <param name="itemNameOrId">The item name or id you want to count</param>
        /// <param name="fullUpdate">Pass false here if you called Inventory.Update before to avoid a second full update.</param>
        /// <returns>InventoryItem if found, null otherwise.</returns>
        public int GetItemCount( object itemNameOrId, bool fullUpdate = true )
        {
            if ( fullUpdate )
            {
                this.Update();
            }
            var id = itemNameOrId is string ? 0 : Convert.ToInt32( itemNameOrId );
            var name = id == 0 ? ( string ) itemNameOrId : string.Empty;
            var count = 0;
            var items = this.bagSlots.Where( b => id > 0 ? b.Id == id : !string.IsNullOrEmpty( name ) ? b.Name == name : 1 == 2 ).ToList();
            if ( items.Count > 0 )
            {
                count = items.Sum( i => i.ItemCount );
            }
            return count;
        }

        public InventoryItem FindItem( object itemNameOrId, string range = null )
        {
            var rango = GetInventoryRange( range );
            if ( rango != null && rango.Location != "inventory" && rango.Location != null )
            {
                //printf("You can only use inventory ranges with 'inventory:findItem'. You cannot use '%s' which is in %s\n", range, location)
            }
            if ( rango == null )
            {
                rango = new InventoryRange( 0, 240, "inventory" );
            }
            InventoryItem smallestStack = null;
            for ( var i = rango.Start; i < rango.End && i < Items.Count; i++ )
            {
                var item = Items[ i ];
                item.Update();
                if ( item.Available && ( !item.InUse ) && ( item.Name.ToLowerInvariant().Contains( itemNameOrId.ToString().ToLowerInvariant() ) || ( itemNameOrId is int && item.Id == ( int ) itemNameOrId ) ) )
                {
                    if ( ( DateTime.Now - item.LastMovedTime ).TotalSeconds > 1 )
                    {
                        if ( item.ItemCount > 1 )
                        {
                            // find smallest stack
                            if ( smallestStack == null || smallestStack.ItemCount > item.ItemCount )
                            {
                                smallestStack = item;
                            }
                        }
                        else
                        {
                            return item;
                        }
                    }
                }
            }
            return smallestStack;
        }

        public bool UseItem( object itemNameOrId )
        {
            var item = this.FindItem( itemNameOrId );
            if ( item != null )
            {
                item.Use();
                return true;//, item.Id, item.Name;
            }
            Bot.Debug( string.Format( "Item: {0} not found.", itemNameOrId ) );
            return false;
        }

        public InventoryRange GetInventoryRange( string range )
        {
            if ( string.IsNullOrEmpty( range ) )
            {
                return null;
            }

            switch ( range )
            {
                case "all":
                    return new InventoryRange( 1, 240, "inventory" );
                case "itemshop":
                    return new InventoryRange( 1, 50, "inventory" );
                case "magicbox":
                    return new InventoryRange( 51, 60, "inventory" );
                case "bag1":
                    return new InventoryRange( 61, 90, "inventory" );
                case "bag2":
                    return new InventoryRange( 91, 120, "inventory" );
                case "bag3":
                    return new InventoryRange( 121, 150, "inventory" );
                case "bag4":
                    return new InventoryRange( 151, 180, "inventory" );
                case "bag5":
                    return new InventoryRange( 181, 210, "inventory" );
                case "bag6":
                    return new InventoryRange( 211, 240, "inventory" );
                case "bags":
                case "bag":
                    return new InventoryRange( 61, 240, "inventory" );
                case "bank1":
                    return new InventoryRange( 1, 40, "bank" );
                case "bank2":
                    return new InventoryRange( 41, 80, "bank" );
                case "bank3":
                    return new InventoryRange( 81, 120, "bank" );
                case "bank4":
                    return new InventoryRange( 121, 160, "bank" );
                case "bank5":
                    return new InventoryRange( 161, 200, "bank" );
                case "banks":
                case "bank":
                    return new InventoryRange( 1, 200, "bank" );
                case "equipment":
                    return new InventoryRange( 0, 21, "equipment" );
                case "head":
                    return new InventoryRange( 0, 0, "equipment" );
                case "hands":
                    return new InventoryRange( 1, 1, "equipment" );
                case "feet":
                    return new InventoryRange( 2, 2, "equipment" );
                case "chest":
                    return new InventoryRange( 3, 3, "equipment" );
                case "legs":
                    return new InventoryRange( 4, 4, "equipment" );
                case "cape":
                    return new InventoryRange( 5, 5, "equipment" );
                case "belt":
                    return new InventoryRange( 6, 6, "equipment" );
                case "shoulders":
                    return new InventoryRange( 7, 7, "equipment" );
                case "necklace":
                    return new InventoryRange( 8, 8, "equipment" );
                case "ammunition":
                    return new InventoryRange( 9, 9, "equipment" );
                case "ranged weapon":
                    return new InventoryRange( 10, 10, "equipment" );
                case "left ring":
                    return new InventoryRange( 11, 11, "equipment" );
                case "right ring":
                    return new InventoryRange( 12, 12, "equipment" );
                case "left earring":
                    return new InventoryRange( 13, 13, "equipment" );
                case "right earring":
                    return new InventoryRange( 14, 14, "equipment" );
                case "main hand":
                    return new InventoryRange( 15, 15, "equipment" );
                case "off hand":
                    return new InventoryRange( 16, 16, "equipment" );
                case "unknown":
                    return new InventoryRange( 17, 17, "equipment" );
                case "amulets":
                    return new InventoryRange( 18, 20, "equipment" );
                case "amulet1":
                    return new InventoryRange( 18, 18, "equipment" );
                case "amulet2":
                    return new InventoryRange( 19, 19, "equipment" );
                case "amulet3":
                    return new InventoryRange( 20, 20, "equipment" );
                case "wings":
                    return new InventoryRange( 21, 21, "equipment" );
            }
            return null;
        }

        public InventoryItem BestAvailableConsumable( ConsumableType type )
        {
            var bestLevel = 0;		// required player level of a potion
            var bestPotency = 0;		// power of a potion
            InventoryItem bestItem = null;
            var bestSmallStack = 999;
            var selectStrategy = "best";

            // set select strategy
            switch ( type )
            {
                case ConsumableType.Mana:
                case ConsumableType.MoT:
                    selectStrategy = Bot.Player.Profile.GetOptionValue<string>( "USE_MANA_POTION" ) == SelectStrategyMinstack ? SelectStrategyMinstack : SelectStrategyDefault;
                    break;
                case ConsumableType.Heal:
                case ConsumableType.HoT:
                    selectStrategy = Bot.Player.Profile.GetOptionValue<string>( "USE_HP_POTION" ) == SelectStrategyMinstack ? SelectStrategyMinstack : SelectStrategyDefault;
                    break;
            }

            this.Update();
            var disponibles = Items.Where( i =>
                                           {
                                               if ( i.Available && i.ItemCount > 0 && Database.Database.Consumables.ContainsKey( i.Id ) )
                                               {
                                                   var consumable = Database.Database.Consumables[ i.Id ];
                                                   if ( consumable.Type == type && consumable.Level <= Bot.Player.Level )
                                                   {
                                                       return true;
                                                   }
                                               }
                                               return false;
                                           } ).Select( i => new { item = i, consumable = Database.Database.Consumables[ i.Id ] } );

            foreach ( var i in disponibles )
            {
                var item = i.item;
                var consumable = i.consumable;

                if ( selectStrategy == SelectStrategyMinstack )
                {
                    // select smallest stack
                    if ( item.ItemCount < bestSmallStack )
                    {
                        bestSmallStack = item.ItemCount;
                        bestItem = item;
                    }
                }
                else
                {	// select best available consumable (& smallest stack by default)
                    // select better level

                    if ( consumable.Level > bestLevel )
                    {
                        bestLevel = consumable.Level;
                        bestPotency = consumable.Potency;
                        bestSmallStack = item.ItemCount;
                        bestItem = item;
                        // same level but select better potency
                    }
                    else if ( consumable.Level == bestLevel &&
                          consumable.Potency > bestPotency )
                    {
                        bestLevel = consumable.Level;
                        bestPotency = consumable.Potency;
                        bestSmallStack = item.ItemCount;
                        bestItem = item;
                        // same/same but select smaller stack
                    }
                    else if ( consumable.Level == bestLevel &&
                          consumable.Potency == bestPotency &&
                          item.ItemCount < bestSmallStack )
                    {
                        bestLevel = consumable.Level;
                        bestPotency = consumable.Potency;
                        bestSmallStack = item.ItemCount;
                        bestItem = item;
                    }
                }
            }

            return bestItem;
        }

        private static Profile Profile
        {
            get { return Bot.Player.Profile; }
        }

        public bool AutoSell( Func<InventoryItem, bool> evalFunc = null )
        {
            if ( !Profile.GetOptionValue<bool>( "INV_AUTOSELL_ENABLE" ) )
            {
                Bot.Warning( Resources.AutoSellDisabled );
                return false;
            }

            if ( Profile.GetOptionValue<int>( "INV_AUTOSELL_TOSLOT" ) > MaxSlots )
            {
                Bot.Warning( string.Format( Resources.InvMaxSlotsDifer, MaxSlots, Profile.GetOptionValue<int>( "INV_AUTOSELL_TOSLOT" ) ) );
            }

            var autoSellQuality = new string[ 0 ];
            if ( !string.IsNullOrWhiteSpace( Profile.GetOptionValue<string>( "INV_AUTOSELL_QUALITY" ) ) )
            {
                autoSellQuality = Profile.GetOptionValue<string>( "INV_AUTOSELL_QUALITY" ).ToLowerInvariant().Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries );
            }

            var noSellStatsNumber = 0;
            if ( !string.IsNullOrWhiteSpace( Profile.GetOptionValue<string>( "INV_AUTOSELL_NOSELL_STATSNUMBER" ) ) )
            {
                noSellStatsNumber = Profile.GetOptionValue<int>( "INV_AUTOSELL_NOSELL_STATSNUMBER" );
            }

            var noSellStatsNames = new string[ 0 ];
            if ( !string.IsNullOrWhiteSpace( Profile.GetOptionValue<string>( "INV_AUTOSELL_NOSELL_STATSNAMES" ) ) )
            {
                noSellStatsNames = Profile.GetOptionValue<string>( "INV_AUTOSELL_NOSELL_STATSNAMES" ).ToLowerInvariant().Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries );
            }

            var noSellTypes = new string[ 0 ];
            if ( !string.IsNullOrWhiteSpace( Profile.GetOptionValue<string>( "INV_AUTOSELL_TYPES_NOSELL" ) ) )
            {
                noSellTypes = Profile.GetOptionValue<string>( "INV_AUTOSELL_TYPES_NOSELL" ).ToLowerInvariant().Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries );
            }

            var sellTypes = new string[ 0 ];
            if ( !string.IsNullOrWhiteSpace( Profile.GetOptionValue<string>( "INV_AUTOSELL_TYPES" ) ) )
            {
                sellTypes = Profile.GetOptionValue<string>( "INV_AUTOSELL_TYPES" ).ToLowerInvariant().Split( new[] { ',' }, StringSplitOptions.RemoveEmptyEntries );
            }

            if ( evalFunc == null )
            {
                evalFunc = item =>
                           {
                               if ( !sellTypes.Any( item.IsType ) )
                               {
                                   Bot.Printf( "{0} - Item not in INV_AUTOSELL_TYPES", item.Name );
                                   return false;
                               }

                               if ( noSellTypes.Any( item.IsType ) )
                               {
                                   Bot.Printf( "{0} - Item is in INV_AUTOSELL_TYPES_NOSELL", item.Name );
                                   return false;
                               }

                               if ( !autoSellQuality.Contains( ColorQuality[ item.Quality ] ) )
                               {
                                   Bot.Printf( "{0} - Item not in INV_AUTOSELL_QUALITY", item.Name );
                                   return false;
                               }

                               if ( noSellStatsNumber > 0 && item.Stats.Count >= noSellStatsNumber )
                               {
                                   Bot.Printf( "{0} - Item.Stats.Count >= INV_AUTOSELL_NOSELL_STATSNUMBER", item.Name );
                                   return false;
                               }

                               if ( item.Stats.Select( s => s.Name.ToLowerInvariant() ).Any( n => noSellStatsNames.Any( n.Contains ) ) )
                               {
                                   Bot.Printf( "{0} - Item.Stats.Names.Contains INV_AUTOSELL_NOSELL_STATSNAMES", item.Name );
                                   return false;
                               }

                               var autoSellNoDura = Profile.GetOptionValue<int>( "INV_AUTOSELL_NOSELL_DURA" );
                               if ( autoSellNoDura > 0 && autoSellNoDura <= item.MaxDurability )
                               {
                                   Bot.Printf( "{0} - Item.MaxDurability >= INV_AUTOSELL_NOSELL_DURA", item.Name );
                                   return false;
                               }

                               var noSellStatsLevel = Profile.GetOptionValue<int>( "INV_AUTOSELL_NOSELL_STATSLEVEL" );
                               if ( noSellStatsLevel > 0 && item.Stats.Any( s => s.Level >= noSellStatsLevel ) )
                               {
                                   Bot.Printf( "{0} - Item.Stats.Level >= INV_AUTOSELL_NOSELL_STATSLEVEL, Stats: {1}", item.Name, string.Join( ", ", item.Stats.Select( s => s.Name ) ) );
                                   return false;
                               }

                               Bot.Printf( Color.Tomato, "Item: {0} will be sold.", item.Name );

                               return true;
                           };
            }

            var fromSlot = Profile.GetOptionValue<int>( "INV_AUTOSELL_FROMSLOT" ) + 60;
            var toSlot = Profile.GetOptionValue<int>( "INV_AUTOSELL_TOSLOT" ) + 60;
            const string sellstartstring = "} local U=UseBagItem; if StoreFrame:IsVisible() then a={true};";
            const string sellendstring = "end;z={";
            var sellString = new StringBuilder( sellstartstring );
            //var sellstring = sellstartstring;
            var hfWesell = false;
            for ( var i = fromSlot - 1; i < toSlot; i++ )
            {
                var item = Items[ i ];
                if ( item != null && item.Id > 0 && !item.InUse && item.CanBeSold && evalFunc( item ) )
                {
                    // Passed eval function. Then sell the item
                    sellString.Append( "U(" + item.BagId + ");" ); // max length 7
                    if ( sellString.Length >= ( 254 - 7 - sellendstring.Length ) )
                    {
                        sellString.Append( sellendstring );
                        if ( Helpers.RoMScript<bool>( sellString.ToString() ) )
                        {
                            Helpers.Yrest( 100 );
                            hfWesell = true;
                            sellString = new StringBuilder( sellstartstring ); // Reset for more
                        }
                        else
                        {
                            return hfWesell;
                        }
                    }
                }
            }

            // Sell any left over in list
            if ( sellString.Length > sellstartstring.Length )
            {
                sellString.Append( sellendstring );
                if ( Helpers.RoMScript<bool>( sellString.ToString() ) )
                {
                    Helpers.Yrest( 100 );
                    hfWesell = true;
                }
            }

            return hfWesell;
        }

        public bool ReloadAmmunition( ConsumableType type )
        {
            if ( type != ConsumableType.Arrow && type != ConsumableType.Thrown )
            {
                Bot.Error( "Bad consumable type passed to ReloadAmmunition: " + type );
                return false;
            }
            var ammoItem = BestAvailableConsumable( type );
            if ( ammoItem == null || ammoItem.ItemCount == 0 )
            {
                var ammoBag = type == ConsumableType.Arrow ? BestAvailableConsumable( ConsumableType.ArrowQuiver ) : BestAvailableConsumable( ConsumableType.ThrownBag );
                if ( ammoBag == null || ammoBag.ItemCount == 0 )
                {
                    Bot.Error( "You don't have more ammunition to load, of type: " + type );
                    return false;
                }
                ammoBag.Use();
                Helpers.Yrest( 250 + Helpers.Ping );
                ammoItem = BestAvailableConsumable( type );
                if ( ammoItem == null || ammoItem.ItemCount == 0 )
                {
                    Bot.Error( "You don't have more ammunition to load, of type: " + type );
                    return false;
                }
            }
            Helpers.Yrest( 250 + Helpers.Ping );
            ammoItem.Use();
            return true;
        }

        public int Money { get; set; }

        public int MaxSlots { get; set; }

        public List<InventoryItem> Items { get { return this.bagSlots; } }

        public bool DoUpdate { get; set; }

        public int EmptySlots
        {
            get
            {
                Update();
                return this.Items.Count == 0 ? 0 : this.Items.Count( i => i.Empty && i.SlotNumber >= 61 && i.SlotNumber <= this.MaxSlots );
            }
        }

        public bool IsFull
        {
            get { return EmptySlots == 0; }
        }
    }

    public class InventoryRange
    {

        public InventoryRange( int start, int end, string location )
        {
            Start = start;
            End = end;
            Location = location;
        }
        public string Location { get; set; }
        public int Start { get; set; }
        public int End { get; set; }
    }
}